////////////////////////////////////////////////
// DESCRIPTION:
//     Devce Memory window information management
//
// Legal Notices:
//     Copyright (c) 2008, Telliam Consulting, LLC.
//     All rights reserved.
//
//     Redistribution and use in source and binary forms, with or without modification,
//     are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//
//     * Redistributions in binary form must reproduce the above copyright notice,
//       this list of conditions and the following disclaimer in the documentation
//       and/or other materials provided with the distribution.
//
//     * Neither the name of Telliam Consulting nor the names of its contributors
//       may be used to endorse or promote products derived from this software
//       without specific prior written permission.
//
// Disclaimer:
//     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
//     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
//     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
//     IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
//     INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
//     BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
//     DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
//     LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
//     OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
//     OF THE POSSIBILITY OF SUCH DAMAGE.  
//

#include <wtypes.h>
#include "FusionWare\DFX\StreamAPI.h"
#include <devload.h>

using namespace FusionWare::Win32;
using namespace FusionWare::DFX;

DeviceWindow::DeviceWindow()
    : InIoSpace(0)
    , WindowSize(0)
    , Stride(0)
    , pVirtBase(0)
{
    this->PhysAddr.QuadPart = 0;
}

DeviceWindow::DeviceWindow(const ParentBusDriver& BusDriver, const PHYSICAL_ADDRESS& PhysAddress, BOOL InIoSpaceVal, UINT32 WindowSizeVal, UINT32 StrideVal)
    : Bus(BusDriver)
    , PhysAddr(PhysAddress)
    , InIoSpace(InIoSpaceVal)
    , WindowSize(WindowSizeVal)
    , Stride(StrideVal)
    , pVirtBase(0)
{
}

DeviceWindow::DeviceWindow(const DeviceWindow& Src)
    : Bus(Src.Bus)
    , PhysAddr(Src.PhysAddr)
    , InIoSpace(Src.InIoSpace)
    , WindowSize(Src.WindowSize)
    , Stride(Src.Stride)
    , pVirtBase(Src.pVirtBase)
{
}

DeviceWindow::~DeviceWindow()
{ 
    FreeDeviceRegs();
}

void DeviceWindow::Init(const ParentBusDriver& BusDriver, const PHYSICAL_ADDRESS& PhysAddress, BOOL InIoSpaceVal, UINT32 WindowSizeVal, UINT32 StrideVal)
{
    this->Bus = BusDriver;
    this->PhysAddr.QuadPart = PhysAddress.QuadPart;
    this->InIoSpace = InIoSpaceVal;
    this->WindowSize = WindowSizeVal;
    this->Stride = StrideVal;
    this->pVirtBase = NULL;
}

DeviceWindow& DeviceWindow::operator=(const DeviceWindow& Src)
{
    this->Bus = Src.Bus;
    this->PhysAddr.QuadPart = Src.PhysAddr.QuadPart;
    this->InIoSpace = Src.InIoSpace;
    this->WindowSize = Src.WindowSize;
    this->Stride = Src.Stride;
    this->pVirtBase = Src.pVirtBase;
    return *this;
}

bool DeviceWindow::IsResourceAvailable()
{
    ResourceAddress Address;
    if (!Bus.GetBusLocation(Address.BusType, Address.BusNumber, Address.SlotNumber))
        return true;

    Address.PhysAddr.QuadPart = this->PhysAddr.QuadPart;
    Address.InIoSpace = this->InIoSpace>0;
    Address.WindowSize = this->WindowSize;

    return FusionWare::DFX::IsResourceAvailable( Address );
}

///////////////////////////////////////////////////////
// Description: 
//    Allocates virtual space for the window and
//    calculates register addresses based on stride
//
// Input: 
//    RegOffsets[] - Array of register offets assuming byte alignment
//    NumRegisters - Number of registers in RegOffset and pRegisters
//
// Output:
//    pRegisters[] - Array of register pointers adjusted for Stride 
//
// Remarks: 
//    This method is used to allocate the virtual address for the
//    device window and to compute register locations based on Stride.
//    This is used for all off chip peripherals and any on-chip ones
//    that have register sets matching standard off chip parts.
//    (E.g. On chip 16550 style UART)
//
bool DeviceWindow::AllocDeviceRegs(const UINT32 RegOffsets[], size_t NumRegisters, void* pRegisters[])
{
    // allocate the virtual base 
    if(!AllocDeviceRegs())
        return false;

    for(int i = 0; i< NumRegisters; ++i)
        pRegisters[i] = (void*)(UINT_PTR(pVirtBase) + RegOffsets[i] * Stride);

    return true;
}

// maps the physical window to virtual in the current process space
// Safe to call multiple times. (Idempotent)
bool DeviceWindow::AllocDeviceRegs()
{
    if(NULL == pVirtBase)
    {
        if(!Bus.TransAddrToVirtual(PhysAddr, WindowSize, (DWORD*)&this->InIoSpace, &pVirtBase))
        {
            pVirtBase = NULL;
            return false;
        }
    }

    return true;
}

void DeviceWindow::FreeDeviceRegs()
{
    if(NULL!=this->pVirtBase && !this->InIoSpace)
    {
        MmUnmapIoSpace(reinterpret_cast<void*>(pVirtBase), this->WindowSize);
        pVirtBase = NULL;
    }
}

bool DeviceWindows::TransAddresses()
{
    int i;
    for(i=0; i< this->NumIoWindows; ++i)
    {
        if(!IoWindows[i].AllocDeviceRegs())
            return false;
    }

    for(i=0; i< this->NumMemWindows; ++i)
    {
        if(!MemoryWindows[i].AllocDeviceRegs())
            return false;
    }

    return true;
}

// TODO:
//     add support for reading PHYSICAL_ADDRESS instead of DWORD for MIPS platforms that
//     use 36 bit addresses. Should have little or no impact on existing code. Probably
//     needs a special reg flag to indicate PHYSICAL_ADDRESS instead of usual DWORD.
//
bool DeviceWindows::LoadFromRegistry( FusionWare::Win32::RegKey& DevKey
                                    , const FusionWare::DFX::ParentBusDriver& ParentBus)
{

    if(!LoadOneWindowFromReg( DevKey
                            , ParentBus
                            , this->MemoryWindows
                            , DEVLOAD_MEMBASE_VALNAME
                            , DEVLOAD_MEMLEN_VALNAME
                            , _T("MemStride")
                            , false
                            , &NumMemWindows
                            )
      )
    {
        return false;
    }

    if(!LoadOneWindowFromReg( DevKey
                            , ParentBus
                            , this->IoWindows
                            , DEVLOAD_IOBASE_VALNAME
                            , DEVLOAD_IOLEN_VALNAME
                            , _T("IoStride")
                            , true
                            , &NumIoWindows
                            )
      )
    {
        return false;
    }

#ifdef DEBUG
    // if there are memory or I/O windows there should be a parent bus
    // (even if it's the "Built-in" one)
    if((NumIoWindows!=0 || NumMemWindows!=0) && !ParentBus.IsValid())
        DEBUGMSG(1, (_T("Memory/IO Windows found but no parent bus! (0xX)\n"), GetLastError()));
#endif

    return true;
}

bool DeviceWindows::LoadOneWindowFromReg( RegKey& Key
                                        , const ParentBusDriver& parentBus
                                        , ce::auto_array_ptr<DeviceWindow>& pMemInfo
                                        , const TCHAR* pBaseName
                                        , const TCHAR* pLenName
                                        , const TCHAR* pStrideName
                                        , bool InIoSpace
                                        , DWORD* pNumWindows
                                        )
{
    // get the size of the arrays in the registry
    DWORD baseSize = Key.QueryValueSize(pBaseName);
    DWORD lenSize = Key.QueryValueSize(pLenName);
    DWORD strideSize = Key.QueryValueSize(pStrideName);

    // If the 3 sizes are different then the registry 
    // data is invalid
    if(baseSize != lenSize || baseSize != strideSize)
    {
        DEBUGMSG(1,(_T("Window registry value sizes don't match! %s=%d, %s=%d\n"), pBaseName, baseSize, pLenName, lenSize));
        return false;
    }

    // OK to have no data - driver or application must validate
    // how many windows it got versus what it expects
    if(0==baseSize)
    {
        *pNumWindows = 0;
        return true;
    }

    // make sure size of address array is a multiple of DWORDS
    if( (baseSize % sizeof(DWORD)) !=0)
    {
        DEBUGMSG(1,(_T("Window registry size not a multiple of a DWORD! Size==%d\n"), baseSize));
        return false;
    }

    // convert from byte size to DWORD array length
    baseSize /= sizeof(DWORD);
    
    // allocate the DeviceWindow array.
    pMemInfo = new DeviceWindow[baseSize];

    // create temp storage for the registry info
    ce::auto_array_ptr<DWORD> pAddresses = new DWORD[baseSize];
    ce::auto_array_ptr<DWORD> pSizes = new DWORD[baseSize];
    ce::auto_array_ptr<DWORD> pStrides = new DWORD[baseSize];

    if(pMemInfo == NULL || pAddresses == NULL || pSizes == NULL || pStrides == NULL)
    {
        DEBUGMSG(1,(_T("DeviceWindows::LoadOneWindowFromReg - FAILED to allocate memory to store registry data\n")));        
        SetLastError(ERROR_OUTOFMEMORY);
        return false;
    }

    // load in the window base address array
    if(!Key.ReadValue(pBaseName, pAddresses, lenSize))
    {
        DEBUGMSG(1,(_T("Failed to load registry values for \"%s\" (0x%X)\n"), pBaseName, GetLastError()));
        return false;
    }

    // load in the Window Size array
    if(!Key.ReadValue(pLenName, pSizes, lenSize))
    {
        DEBUGMSG(1,(_T("Failed to load registry values for \"%s\" (0x%X)\n"), pBaseName, GetLastError()));
        return false;
    }

    // load in the windowStride information
    if(!Key.ReadValue(pStrideName, pStrides, lenSize))
    {
        DEBUGMSG(1,(_T("Failed to load registry values for \"%s\" (0x%X)\n"), pBaseName, GetLastError()));
        return false;
    }

    // finally, fill in the window info array elements from the reg info
    for(int i=0; i< baseSize; ++i)
    {
        PHYSICAL_ADDRESS physAddr = {pAddresses[i], 0};
        pMemInfo[i].Init(parentBus, physAddr, ULONG(InIoSpace), pSizes[i], pStrides[i]);
    }

    *pNumWindows = baseSize;
    return true;
}
